
/** 使用指定编码读取数据
 * @param {ArrayBuffer} buffer
 * @param {string} [encoding] - 读取文本的编码格式，默认 utf-8，读取 csv 可以使用 gb2312
 */
export function readText(buffer, encoding) {
  return new TextDecoder(encoding).decode(buffer);
}
/** 导入文件
 * @template T
 * @param {function(string):Promise<T>} func - 对读取的文件内容做处理
 * @param {string} [accept] - 读取文件的后缀名，格式为 .后缀1,.后缀2
 * @param {boolean} [multiple] - 是否允许读取多个文件
 * @param {string} [encoding] - 读取文本的编码格式，默认 utf-8，读取 csv 可以使用 gb2312
 * @returns {Promise<T[]>}
 */
export function upload(func, accept, multiple, encoding) {
  return new Promise((resolve, reject) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = accept;
    input.multiple = multiple;
    input.onchange = (e) => {
      var reader = new FileReader();
      const files = e.target.files;
      const results = [];
      // 读取文件内容
      reader.onload = async () => {
        results.push(await func(readText(reader.result, encoding)));
        if (results.length == files.length)
          resolve(results);
      };
      for (const file of files)
        reader.readAsArrayBuffer(file)
    };
    input.oncancel = (e) => {
      reject('导入文件已取消');
    };
    input.click();
  });
}
/** 导入 CSV 文件转换成对象
 * @template T
 * @param {number} [title] - 字段名所在行数，默认为第一行，后面将认为是数据
 * @returns {Promise<T[]>}
 */
export function uploadCSV(title) {
  return upload(csv => fromCSV(csv, title), '.csv', false, 'gb2312').then(i => i[0]);
}
/** 将字符串内容作为文件下载
 * @param {string} text - 字符串内容
 * @param {string} [filename] - 文件名
 */
export function download(text, filename) {
  return downloadURL(URL.createObjectURL(new Blob([text], { type: 'text/plain' })), filename || 'download.txt');
}
/** 下载链接的文件
 * @param {string} url - 地址
 * @param {string} [filename] - 文件名
 */
export async function downloadURL(url, filename) {
  if (!url.startsWith('blob:'))
    url = await fetch(url).then(res => res.blob()).then(blob => URL.createObjectURL(blob));
  const a = window.document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  return url;
}
/** 读取 CSV 字符串
 * @param {string} csv - CSV 文件字符串
 * @param {number} [title] - 字段名所在行数，默认为第一行，后面将认为是数据
 */
export function fromCSV(csv, title = 1) {
  let index = 0;  // 读取到的索引
  /** 读取下一个单元格的内容，后面的逗号或换行没有读 */
  function readGrid() {
    const c = csv.charAt(index);
    if (c == '"') {
      // 有特殊字符
      index++;

      let temp = index;
      // 读取到单元格末尾的引号，防止读取到单元格中的引号字符
      while (true) {
        const i1 = csv.indexOf('"', temp);
        const i2 = csv.indexOf('""', temp);
        if (i1 == i2)
          temp = i2 + 2;
        else {
          temp = i1;
          break;
        }
      }

      const result = csv.substring(index, temp);
      index = temp + 1;
      return result;
    } else {
      const from = index;
      // 没有特殊字符直接读取到 ,"\n
      for (; index < csv.length; index++)
        if ('",\r\n'.includes(csv.charAt(index)))
          break;
      return csv.substring(from, index);
    }
  }
  /** 读取完单元格后，判断后面是分隔单元格(true)还是换行(false) */
  function isSplitOrLine() {
    const c = csv.charAt(index++);
    if (c == ',')
      return true;
    else if (c == '\r') {
      index++;
      return false;
    } else if (c == '\n' || index >= csv.length)
      return false;
    else
      throw `分割单元格错误的字符 ${c}:${index - 1}`;
  }

  const results = [];
  const keys = [];
  // 读取表头
  for (let i = 1; i <= title; i++) {
    do {
      const grid = readGrid();
      if (i == title)
        keys.push(grid);
    } while (isSplitOrLine())
  }
  // 读取数据
  while (index < csv.length) {
    const result = {};
    for (let i = 0; i < keys.length; i++) {
      const grid = readGrid();
      result[keys[i]] = grid;
      isSplitOrLine();
    }
    results.push(result);
  }

  return results;
}
/** 将对象数组转换成 CSV
 * @template T
 * @param {T[]} objs - 对象数组
 * @param {{key: string, label?: string, desc?: string, value?: (data: T, index:number) => any }[]} [title] - 自定义表头；key 为对象字段名，label 为表头文字，desc 为字段名前单独一行的注释(推荐)；空则用对象字段做表头，空数组代表不要表头
 */
export function toCSV(objs, title) {
  /** 将字符串转换成 CSV 的字符串
   * @param {string} str 
   */
  function toCSVString(str) {
    if (!str) return undefined;
    str = str.toString();
    if (str.includes('\r') || str.includes('\n') || str.includes('\"') > 0 || str.includes(','))
      return `"${str.replace(/"/g, '""').replace(/\r\n/g, '\n').replace(/\r/g, '\n')}"`;
    else
      return str;
  }

  const lines = [];
  const line = [];
  // 表头
  const keys = [];
  if (title) {
    if (title.some(i => i.desc)) {
      for (const item of title)
        line.push(toCSVString(item.desc));
      lines.push(line.join(','));
      line.length = 0;
    }
    for (const item of title) {
      keys.push(item.key);
      line.push(toCSVString(item.label || item.key));
    }
    // 自定义表头
    if (line.length)
      lines.push(line.join(','));
    // 只有 keys 不写入表头
    else
      keys.push(...Object.keys(objs[0]));
  }
  // 对象字段做表头
  if (!keys.length) {
    for (const key in objs[0]) {
      keys.push(key);
      line.push(toCSVString(key));
    }
    lines.push(line.join(','));
  }

  // 写入数据
  for (const item of objs) {
    line.length = 0;
    for (const key of keys) {
      line.push(toCSVString(item[key]))
    }
    lines.push(line.join(','));
  }

  return lines.join('\r\n');
}